/********************************************************************************
* Project       : No Latency RTSP streaming Project
* Author        : Seo, SeokWon 
* e-mail        : kueony@gmail.com
* Licence       : See the Copyright
*********************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/msg.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>

#include "vll_protocol.h"
#include "vll_sc_protocol.h"
#include "vll_server_acceptor.h"

#include "lib_log.h"
#include "lib_str.h"
#include "lib_network.h"

#define MSG_TIMEOUT						5000
#define MSET(x) memset (&(x), 0, sizeof(x))

typedef struct pollfd	st_PollFd;


// ****************************************************************
// .. functions
// ****************************************************************
static void*	AcceptorThread(void* arg);
static int		InitNetwork(st_AcceptorThreadInfo* a_pstInfo);
static int		AcceptClient(st_AcceptorThreadInfo* a_pstInfo, int a_iServerSock);

extern int		MakeServerSocket(char* a_pcAddr, int a_iPort);
extern int		SendMsg4FixSize(int a_iSock, char *a_pcData, int a_iSize);
extern int		RecvMsg4FixSize(int a_iSock, char *a_pcData, int a_iSize);

// ****************************************************************
// .. Run Thread
// ****************************************************************
int RunAcceptorThread(st_AcceptorThreadInfo* a_pstInfo)
{
	if(InitNetwork(a_pstInfo) < 0)
	{
		LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "init_network");
		return -1;
	}

	if(pthread_create(&a_pstInfo->Thread_t, NULL, &AcceptorThread, (void *) a_pstInfo) < 0)
	{
		LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "create_thread");
		return -1;
	}

	while(a_pstInfo->iOperationStatus == 0) usleep(1);

	LogMsg(LIB_LOG_TYPE_MSG, "(%s, %d) %s", __FILE__, __LINE__, "run_ok");
	
	return 0;
}


// ****************************************************************
// .. Close Thread
// ****************************************************************
int	CloseAcceptorThread(st_AcceptorThreadInfo* a_pstInfo)
{
	if(a_pstInfo->iOperationStatus == 1 || a_pstInfo->iOperationStatus == -1)
	{
		a_pstInfo->iOperationStatus = 0;
		if(a_pstInfo->iServSock > 2) SOCKET_ALL_CLOSE(a_pstInfo->iServSock);
		pthread_join(a_pstInfo->Thread_t, NULL);
		LogMsg(LIB_LOG_TYPE_MSG, "(%s, %d) %s", __FILE__, __LINE__, "join_thread_ok");
	}
	else
	{
		if(a_pstInfo->iServSock > 2) SOCKET_ALL_CLOSE(a_pstInfo->iServSock);
	}

	LogMsg(LIB_LOG_TYPE_MSG, "(%s, %d) %s", __FILE__, __LINE__, "close_thread_ok");

	return 0;
}

// ****************************************************************
// .. Acceptor Thread
// ****************************************************************
static void* AcceptorThread(void *arg)
{
	st_AcceptorThreadInfo*	pstInfo = NULL;
	struct pollfd			stPoll;
	int						iResult;
	
	pstInfo = (st_AcceptorThreadInfo*) arg;

	bzero(&stPoll, sizeof(stPoll));

	stPoll.fd		= pstInfo->iServSock;
	stPoll.events	= POLLIN;

	pstInfo->iOperationStatus = 1;
	
	// main routine
	while(pstInfo->iOperationStatus == 1)
	{
		if(poll(&stPoll, 1, -1) <= 0) // .. BLOCK Mode - accept client's request to make a connection
		{
			if(pstInfo->iOperationStatus == 0) break;
			else if(errno != EINTR)
			{
				LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "poll_fail");
			}
			continue;
		}
		
		if(stPoll.revents & (POLLNVAL | POLLHUP | POLLERR))
		{
			if(pstInfo->iOperationStatus == 0) break;

			LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "poll_revent");
			pstInfo->iOperationStatus = -1;
			LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "stop_acceptor_operation");
		}
		
		if(stPoll.revents & POLLIN)
		{
			if((iResult = AcceptClient(pstInfo, stPoll.fd)) < 0)
			{
				LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "fail_accept_client");

				if(iResult == -2)
				{
					LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "connection_with_main_module");
					pstInfo->iOperationStatus = -1;
					LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "stop_acceptor_operation");
				}
			}
		}
	}

	return NULL;
}


// ****************************************************************
// .. Accept New Client
// ****************************************************************
static int AcceptClient(st_AcceptorThreadInfo* a_pstInfo, int a_iServerSock)
{
	int					iAcceptorSock;
	struct sockaddr_in	stAcceptorAddr;
	int					iAcceptorLen;
	st_VllMsg			stMsg;
	st_ServerClientMsg	stClientMsg;

	MSET(stMsg);
	MSET(stClientMsg);
	iAcceptorLen = sizeof(stAcceptorAddr);
	
	bzero(&stAcceptorAddr, sizeof(stAcceptorAddr)); 
	iAcceptorSock = accept(a_iServerSock, (struct sockaddr *)&stAcceptorAddr, (socklen_t *) &iAcceptorLen);

	if(iAcceptorSock < 0)
	{
		LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "fail_accept");
		return -1;
	}
	
	if(RecvMsg4FixSize(iAcceptorSock, (char *)&stClientMsg, sizeof(stClientMsg)) < 0)
	{
		LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "fail_recv_first_msg");
		SOCKET_ALL_CLOSE(iAcceptorSock);
		return -1;
	}
	
	stMsg.opcode	= stClientMsg.opcode;
	stMsg.sock		= iAcceptorSock;
	
	if(SendMsg4FixSize(a_pstInfo->iaSock[1], (char *)&stMsg, sizeof(stMsg)) < 0)
	{
		LogMsg(LIB_LOG_TYPE_ERROR, "(%s, %d) %s", __FILE__, __LINE__, "send_msg_to_main_module");
		return -2;
	}
	
	LogMsg(LIB_LOG_TYPE_MSG, "(%s, %d) %s", __FILE__, __LINE__, "accept_client");
	
	return 0;
}


// ****************************************************************
// .. Initialize Server Socket
// ****************************************************************
static int InitNetwork(st_AcceptorThreadInfo* a_pstInfo)
{
	int iSockNic = 0;

	if((iSockNic = MakeServerSocket(a_pstInfo->pcServIp, a_pstInfo->iServPort)) < 0)
	{
		LogMsg(LIB_LOG_TYPE_DEBUG, "(%s, %d) %s", __FILE__, __LINE__, "fail_make_server_socker");
		return -1;
	}
	
	a_pstInfo->iServSock = iSockNic;

	return 0;
}
